package com.zuipin.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.URL;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.servlet.http.HttpServletRequest;

import org.apache.http.conn.ssl.SSLContexts;
import org.apache.log4j.Logger;
import org.springframework.core.io.ClassPathResource;

import com.alibaba.fastjson.JSONObject;
import com.zuipin.framework.emuns.ErrorCode;
import com.zuipin.framework.exception.CustomException;

import net.sf.json.xml.XMLSerializer;

/**
 * 
 * @author xujinding
 * @date 2017年04月05日 下午1:52:54
 * @Description: 微信 工具
 */
public class WechatUtil {
	
	public static final String	URL_FOR_UNIFIED_ORDER;										//统一支付接口
	public static final String	URL_FOR_TRANSFERS;											//企业付款接口
	
	public static final String	WECHAT_PAY_CERTIFICATE_PATH;								//微信证书目录
	
	public static final String	URL_FOR_REFUND_BACK_ORDER;									//退款接口
	public static final Integer	TIME_OUT			= 30 * 1000;							//30s
	public static Logger		log					= Logger.getLogger(WechatUtil.class);
	public static final String	HEX					= "0123456789abcdef";
	public static final String	NATIVE_TRADE_TYPE	= "NATIVE";								//交易类型
	public static final Integer	CHECK_OPTION_NO		= 1;
	
	static {
		
		URL_FOR_UNIFIED_ORDER = "https://api.mch.weixin.qq.com/pay/unifiedorder";
		URL_FOR_REFUND_BACK_ORDER = "https://api.mch.weixin.qq.com/secapi/pay/refund";
		URL_FOR_TRANSFERS = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
		WECHAT_PAY_CERTIFICATE_PATH = "apiclient_cert.p12";
	}
	
	/**
	 * 
	 * @Title: comeFromWechat
	 * @Description: 判断请求是否来自微信浏览器
	 * @author: xujinding
	 * @date: 2016年04月05日 下午1:53:21
	 * @param request
	 * @return
	 * @return boolean
	 */
	public static boolean comeFromWechat(HttpServletRequest request) {
		String userAgent = request.getHeader("User-Agent");
		// 添加小写获取
		if (userAgent == null) {
			userAgent = request.getHeader("user-agent");
		}
		if (userAgent != null) {
			userAgent = userAgent.toLowerCase();
		}
		// 添加判空
		if (userAgent != null && userAgent.indexOf("micromessenger") >= 0) {
			return true;
		}
		return false;
	}
	
	/**
	 * 调用微信统一支付接口
	 * 
	 * @Title: callUnifiedOrder
	 * @author xujinding
	 * @date 2017年04月06日 下午4:26:35
	 * @param payAmount 支付金额 单位元
	 * @param orderNo 订单号
	 * @param notifyUrlkey 回调url key
	 * @param openId 微信用户标识
	 * @param createIp 微信用户Ip
	 * @param proDes 商品描述
	 * @return
	 * @return JSONObject
	 * @throws UnsupportedEncodingException 
	 * @throws NoSuchAlgorithmException 
	 * @throws Exception
	 */
	public static JSONObject callUnifiedOrder(Double payAmount, String payNo, String openId,
			String createIp, String proDes, Map<String, String> payTool, String tradeType)
			throws CustomException, NoSuchAlgorithmException, UnsupportedEncodingException {
		
		Map<String, Object> map = new HashMap<String, Object>();
		// appid 公众号Id
		map.put("appid", payTool.get("appId"));
		// mch_id 商户号
		map.put("mch_id", payTool.get("mchId"));
		// 随机字符串 32位以内、、我们推荐生成随机数算法如下：调用随机数函数生成，将得到的值转换为字符串。
		map.put("nonce_str", createRandom(30));
		// openid 微信客户openId 可以忽略 但是trade_type=JSAPI时（即公众号支付），此参数必传，
		map.put("openid", openId);
		// 回调url
		map.put("notify_url", payTool.get("notifyUrl"));
		// orderNo 订单编号
		map.put("out_trade_no", payNo);
		// total_fee 金额 单位 转成 分
		map.put("total_fee", Double.valueOf((payAmount * 100)).intValue());
		// createIp 请求客户端Id
		map.put("spbill_create_ip", createIp);
		// body 简要描述
		if (proDes != null) {
			if (proDes.length() > 128) {
				map.put("body", proDes.substring(0, 128));
			} else {
				map.put("body", proDes);
			}
		} else {
			// 必填 不能为空
			map.put("body", proDes);
		}
		
		// trade_type 交易类型--trade_type=JSAPI时（即公众号支付），此参数必传，
		map.put("trade_type", tradeType);
		// 公众号 后台 支付设置的秘钥
		String key = payTool.get("keyValue");
		// 将参数内容组装成xml
		StringBuffer xml = new StringBuffer();
		xml.append("<xml>");
		for (Entry<String, Object> ent : map.entrySet()) {
			xml.append("<" + ent.getKey() + ">" + ent.getValue() + "</" + ent.getKey() + ">");
		}
		// 生成签名
		xml.append("<sign>" + createSign(map, key) + "</sign>");
		xml.append("</xml>");
		// 获取返回的xml
		String resutl = HttpUtil.doRequest(URL_FOR_UNIFIED_ORDER, "POST", xml.toString(), null);
		//System.out.println(resutl);
		JSONObject jsOb = null;
		if (resutl != null) {
			jsOb = JSONObject.parseObject(xml2JSON(resutl).toString());
			String str = jsOb.getString("return_code");
			if (str != null && "SUCCESS".equals(str.trim())) {
				str = jsOb.getString("result_code");
				if (str != null && "SUCCESS".equals(str.trim())) {
					String prepayId = jsOb.getString("prepay_id");// 获得预支付Id
					if (prepayId != null && !"".equals(prepayId.trim())) {
						Map<String, Object> resultMap = new HashMap<String, Object>();
						resultMap.put("appId", jsOb.getString("appid"));// 公众号ID
						resultMap.put("timeStamp", Long.toString(new Date().getTime()));// 时间戳
						resultMap.put("nonceStr", createRandom(30));// 随机数 32位以内
						resultMap.put("prepayId", prepayId);// 预支付Id
						resultMap.put("signType", "MD5");// 加密算法 目前只支持MD5
						resultMap.put("paySign", createSign(resultMap, key));// 生成签名
						resultMap.put("payNo", payNo);// 返回订单编号
						resultMap.put("payAmount", payAmount); // 返回支付金额
						if (NATIVE_TRADE_TYPE.equals(tradeType)) {
							resultMap.put("tradeType", jsOb.getString("code_url"));
						}
						return JSONObject.parseObject(JSONObject.toJSONString(resultMap));
					}
				}
			}
		}
		// 打印请求和返回值
		log.info("request url:" + URL_FOR_UNIFIED_ORDER);
		log.info("request params:" + xml.toString());
		log.info("response resutl:" + resutl);
		// 打印请求和返回值
		throw new CustomException(ErrorCode.ORDER_ERRPR.getCode(), "统一下单失败");
	}
	
	/**
	 * 企业付款接口
	 * 
	 * @Title: callTransfers
	 * @author xujinding
	 * @date 2017年8月8日 上午11:40:51
	 * @param payAmount
	 *            支付金额
	 * @param orderNo
	 *            订单编号
	 * @param checkOption
	 *            检验用户名选项 1和其他不检验 2 强校验真实姓名 3针对已实名认证的用户才校验真实姓名
	 * @param custName
	 *            收款用户真实姓名。
	 * @param openId
	 *            用户的openid
	 * @param createIp
	 *            调用接口的机器Ip地址
	 * @param remark
	 *            付款说明信息
	 * @return
	 * @throws Exception
	 * @return JSONObject
	 * @throws UnsupportedEncodingException 
	 * @throws NoSuchAlgorithmException 
	 * @throws BussniseException 
	 */
	public static JSONObject callTransfers(String appId, String mchid, String key, Double payAmount,
			String orderNo, Integer checkOption, String custName, String openId, String createIp,
			String remark)
			throws CustomException, NoSuchAlgorithmException, UnsupportedEncodingException {
		Map<String, Object> map = new HashMap<String, Object>();
		
		//String appid = propertiesManager.getString("wechat_appid");
		// appid 公众号Id
		map.put("mch_appid", appId);
		// mch_id 商户号
		map.put("mchid", mchid);
		// 随机字符串 32位以内
		map.put("nonce_str", createRandom(30));
		
		// orderNo 订单编号
		map.put("partner_trade_no", orderNo);
		// 用户Id
		map.put("openid", openId);
		
		// 是否检验名称
		if (checkOption == 1) {
			map.put("check_name", "NO_CHECK");
			map.put("re_user_name", custName);
		} else if (checkOption == 2) {
			map.put("check_name", "FORCE_CHECK");
			map.put("re_user_name", custName);
		} else if (checkOption == 3) {
			map.put("check_name", "OPTION_CHECK");
			map.put("re_user_name", custName);
		} else {
			map.put("check_name", "NO_CHECK");
			map.put("re_user_name", custName);
		}
		
		// 金额
		map.put("amount", Double.valueOf((payAmount * 100)).intValue());
		// 说明
		map.put("desc", remark);
		// 客户Ip
		map.put("spbill_create_ip", createIp);
		
		// 公众号 后台 支付设置的秘钥
		//String key = propertiesManager.getString("wechat_pay_key");
		// 将参数内容组装成xml
		StringBuffer xml = new StringBuffer();
		xml.append("<xml>");
		for (Entry<String, Object> ent : map.entrySet()) {
			xml.append("<" + ent.getKey() + ">" + ent.getValue() + "</" + ent.getKey() + ">");
		}
		// 生成签名
		xml.append("<sign>" + createSign(map, key) + "</sign>");
		xml.append("</xml>");
		// 获取返回的xml
		String resutl = doRequestByWeChatCertificate(URL_FOR_TRANSFERS, "POST", xml.toString(),
				null, WECHAT_PAY_CERTIFICATE_PATH, mchid);
		JSONObject jsOb = null;
		if (resutl != null) {
			jsOb = JSONObject.parseObject(xml2JSON(resutl).toString());
			String str = jsOb.getString("return_code");
			if (str != null && "SUCCESS".equals(str.trim())) {
				str = jsOb.getString("result_code");
				String errCode = jsOb.getString("err_code");
				if (str != null && "SUCCESS".equals(str.trim())) {
					return jsOb;
				} else if (errCode != null && "SYSTEMERROR".equals(errCode.trim())) {
					log.info("系统错误重新---开始");
					int num = 1;//系统错误，，重新回调次数
					JSONObject rejs = systemErrorMethod(xml.toString(), mchid, num);
					if (rejs == null) {
						throw new CustomException(ErrorCode.DRAW_FAIL.getCode(), "提现失败--系统错误");
					} else {
						str = rejs.getString("return_code");
						if (str != null && "SUCCESS".equals(str.trim())) {
							str = jsOb.getString("result_code");
							if (str != null && "SUCCESS".equals(str.trim())) {
								return jsOb;
							}
						}
					}
				}
			}
		}
		// 打印请求和返回值
		log.error("request url:" + URL_FOR_UNIFIED_ORDER);
		log.error("request params:" + xml.toString());
		log.error("response resutl:" + resutl);
		throw new CustomException(ErrorCode.DRAW_FAIL.getCode(),
				"转账失败 !!!入参：" + xml.toString() + "出参：" + resutl);
	}
	
	/**
	 * 
	 * @param xml
	 * @param mchid
	 * @param num  重新回调
	 * @return
	 */
	public static JSONObject systemErrorMethod(String xml, String mchid, int num) {
		JSONObject jsOb = null;
		if (num <= 3) {
			String reResutl = doRequestByWeChatCertificate(URL_FOR_TRANSFERS, "POST",
					xml.toString(), null, WECHAT_PAY_CERTIFICATE_PATH, mchid);
			log.error("num==" + num + "  system error recall:" + reResutl);
			if (reResutl != null) {
				jsOb = JSONObject.parseObject(xml2JSON(reResutl).toString());
				String str = jsOb.getString("return_code");
				if (str != null && "SUCCESS".equals(str.trim())) {
					str = jsOb.getString("result_code");
					String errCode = jsOb.getString("err_code");
					if (str != null && "SUCCESS".equals(str.trim())) {
						return jsOb;
					} else if (errCode != null && "SYSTEMERROR".equals(errCode.trim())) {
						num = num + 1;
						systemErrorMethod(xml, mchid, num);
					}
				}
			}
		}
		return jsOb;
	}
	
	public static String caiBeiMD5(String value) {
		char hexDigits[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D',
				'E', 'F' };
		try {
			byte[] btInput = value.getBytes();
			// 获得MD5摘要算法的 MessageDigest 对象
			MessageDigest mdInst = MessageDigest.getInstance("MD5");
			// 使用指定的字节更新摘要
			mdInst.update(btInput);
			// 获得密文
			byte[] md = mdInst.digest();
			// 把密文转换成十六进制的字符串形式
			int j = md.length;
			char str[] = new char[j * 2];
			int k = 0;
			for (int i = 0; i < j; i++) {
				byte byte0 = md[i];
				str[k++] = hexDigits[byte0 >>> 4 & 0xf];
				str[k++] = hexDigits[byte0 & 0xf];
			}
			return new String(str);
		} catch (Exception e) {
			// e.printStackTrace();
			return null;
		}
	}
	
	/**
	 * 创建包含数字和字母的随机数， 如果是字母全部是大写
	 * 
	 * @author: xujinding
	 * @date: 2017年04月06日
	 * @param len 随机数的长度
	 * @return
	 * @throws
	 */
	public static String createRandom(int len) {
		StringBuffer sBuffer = new StringBuffer();
		Random random = new Random();
		for (int i = 0; i < len; i++) {
			// 输出字母还是数字
			String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
			
			if ("char".equalsIgnoreCase(charOrNum)) { // 字符串
				// 取得大写字母还是小写字母
				// int choice = random.nextInt(2) % 2 == 0 ? 65 : 97;
				int choice = 65; // 全部大写
				sBuffer.append((char) (choice + random.nextInt(26)));
			} else if ("num".equalsIgnoreCase(charOrNum)) { // 数字
				sBuffer.append(String.valueOf(random.nextInt(10)));
			}
		}
		return sBuffer.toString();
	}
	
	/**
	 * 将参数 加key 后生成 签名
	 * 
	 * @Title: createSign
	 * @author xujinding
	 * @date 2017年04月06日 上午9:49:37
	 * @param map 参数
	 * @param key
	 * @return
	 * @return String 微信签名
	 * @throws NoSuchAlgorithmException 
	 * @throws UnsupportedEncodingException 
	 */
	public static String createSign(Map<String, Object> map, String key)
			throws NoSuchAlgorithmException, UnsupportedEncodingException {
		
		// 通过ArrayList构造函数把map.entrySet()转换成list
		List<Entry<String, Object>> mappingList = new ArrayList<Entry<String, Object>>(
				map.entrySet());
		
		// 通过比较器实现比较 排序
		Collections.sort(mappingList, new Comparator<Entry<String, Object>>() {
			public int compare(Entry<String, Object> mapping1, Entry<String, Object> mapping2) {
				return mapping1.getKey().compareTo(mapping2.getKey());
			}
		});
		// 组装成字符串
		String str = "";
		for (Entry<String, Object> ent : mappingList) {
			str += ent.getKey() + "=" + ent.getValue() + "&";
		}
		//String sign = caiBeiMD5(str + "key=" + key);
		// MD5 加密生成签名
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		byte[] signByte = md5.digest((str + "key=" + key).getBytes("utf-8"));
		//将字节转成16进制的字符串;
		String sign = bytes2hex(signByte).toUpperCase();
		System.out.println(sign);
		return sign;
	}
	
	/**
	 * XOM 包 将 xml 转成 json 字符串
	 * 
	 * @Title: xml2JSON
	 * @author xujinding
	 * @date 2017年04月06日 上午9:00:43
	 * @param xml xml内容
	 * @return
	 * @return JSON
	 * @throws ClassNotFtStreamException
	 */
	public static net.sf.json.JSON xml2JSON(String xml) {
		return new XMLSerializer().read(xml);
	}
	
	/**
	 * 
	* @Description:
	* @Title: 
	* @author: xujinding
	* @date: 2017年4月6日上午11:50:00
	 */
	public static String bytes2hex(byte[] bytes) {
		StringBuilder sb = new StringBuilder(bytes.length * 2);
		for (byte b : bytes) {
			// 取出这个字节的高4位，然后与0x0f与运算，得到一个0-15之间的数据，通过HEX.charAt(0-15)即为16进制数    
			sb.append(HEX.charAt((b >> 4) & 0x0f));
			// 取出这个字节的低位，与0x0f与运算，得到一个0-15之间的数据，通过HEX.charAt(0-15)即为16进制数    
			sb.append(HEX.charAt(b & 0x0f));
		}
		return sb.toString();
	}
	
	/**
	 * 返回String 加载微信支付证书 请求
	 * 
	 * @Title: doRequestByWeChatCertificate
	 * @author xujinding
	 * @date 2017年6月8日 下午6:07:33
	 * @param requestUrl
	 *            调用url
	 * @param requestMethod
	 *            调用方法
	 * @param params
	 *            参数
	 * @param timeOut
	 *            超时时间 默认30秒
	 * @param filePath
	 *            证书路径
	 * @param mchId
	 *            商品ID
	 * @return
	 * @return String
	 */
	public static String doRequestByWeChatCertificate(String requestUrl, String requestMethod,
			String params, Integer timeOut, String filePath, String mchId) {
		
		try {
			
			KeyStore keyStore = KeyStore.getInstance("PKCS12");
			//FileInputStream instream = new FileInputStream(ResourceUtils.getFile(filePath));
			ClassPathResource classPathResource = new ClassPathResource(filePath);
			InputStream instream = classPathResource.getInputStream();
			try {
				keyStore.load(instream, mchId.toCharArray());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				instream.close();
			}
			SSLContext sslContext = SSLContexts.custom()
					.loadKeyMaterial(keyStore, mchId.toCharArray()).build();
			
			// 从上述SSLContext对象中得到SSLSocketFactory对象
			SSLSocketFactory ssf = sslContext.getSocketFactory();
			URL url = new URL(requestUrl);
			HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
			conn.setSSLSocketFactory(ssf);
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setUseCaches(false);
			if (timeOut == null) {
				timeOut = TIME_OUT;
			}
			conn.setConnectTimeout(timeOut);
			conn.setReadTimeout(timeOut);
			// 设置请求方式（GET/POST）
			conn.setRequestMethod(requestMethod);
			// conn.setRequestProperty("content-type", "application/x-www-form-urlencoded");
			// 当outputStr不为null时向输出流写数据
			if (null != params) {
				OutputStream outputStream = conn.getOutputStream();
				// 注意编码格式
				outputStream.write(params.getBytes("UTF-8"));
				outputStream.close();
			}
			// 从输入流读取返回内容
			InputStream inputStream = conn.getInputStream();
			InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
			BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
			String str = null;
			StringBuffer buffer = new StringBuffer();
			while ((str = bufferedReader.readLine()) != null) {
				buffer.append(str);
			}
			// 释放资源
			bufferedReader.close();
			inputStreamReader.close();
			inputStream.close();
			inputStream = null;
			conn.disconnect();
			return buffer.toString();
		} catch (ConnectException ce) {
			log.error("连接超时：", ce);
		} catch (Exception e) {
			log.error("https请求异常：", e);
		}
		return null;
	}
}
